﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;

namespace Heng.Elements.Core.IOC
{
    /// <summary>
    /// Dependency container to hold all dependencies.
    /// Designed as a facade arround MEF.
    /// </summary>
    public class Ioc
    {
        #region Member Variables
        // Singleton members
        static object _instanceSyncObj = new object();
        static volatile Ioc _instance = null;
        #endregion

        #region Properties
        /// <summary>
        /// Singleton instance.
        /// </summary>
        public static Ioc Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_instanceSyncObj)
                    {
                        if (_instance == null)
                        {
                            // create singleton instance
                            Ioc instance = new Ioc();

                            // configure container
                            instance.Configure();

                            // assign singleton instance
                            _instance = instance;
                        }
                    }
                }

                return _instance;
            }
        }

        /// <summary>
        /// Composition container used to keep dependencies.
        /// </summary>
        private CompositionContainer Container { get; set; }

        /// <summary>
        /// Aggregate catalog used to hold multiple catalogs each with its bindings.
        /// </summary>
        private AggregateCatalog AggregateCatalog { get; set; }

        /// <summary>
        /// Bin directory where assemblies are located.
        /// </summary>
        public static string BinDirectory
        {
            get
            {
                string dir;

                // determine if is Web application
                if (HttpContext.Current == null)
                {
                    // NON-WEB
                    dir = AppDomain.CurrentDomain.BaseDirectory;
                }
                else
                {
                    // WEB
                    dir = HttpRuntime.BinDirectory;
                }

                return dir;
            }
        }
        #endregion

        public T GetInstance<T>()
        {
            return Container.GetExportedValue<T>();
        }

        public IEnumerable<T> GetAllInstances<T>()
        {
            return Container.GetExportedValues<T>();
        }

        /// <summary>
        /// Bind T to all types inheriting from T.
        /// </summary>
        /// <typeparam name="T"> Type used for binding. </typeparam>
        public void AddBindingsFor<T>()
        {
            AddBindings((regBuilder) =>
                {
                    // create rule for loading basic plugins (: IDependencyContainerPlugins)
                    regBuilder.ForTypesDerivedFrom<T>().ExportInterfaces();
                });
        }

        /// <summary>
        /// Bind using an action delegate.
        /// </summary>
        /// <param name="registrationAction"> Registration action delegate used to implement the logic of binding. </param>
        public void AddBindings(Action<RegistrationBuilder> registrationAction)
        {
            // register bindings
            RegistrationBuilder regBuilder = new RegistrationBuilder();
            registrationAction(regBuilder);

            // create directory catalog to load bindings
            string binDirectory = BinDirectory;
            DirectoryCatalog dirCat = new DirectoryCatalog(binDirectory, regBuilder);
            AggregateCatalog.Catalogs.Add(dirCat);
        }

        #region Helper Methods
        private void Configure()
        {
            // create aggregate catalog to hold multiple catalogs
            AggregateCatalog = new AggregateCatalog();            

            // create container
            Container = new CompositionContainer(AggregateCatalog);

            // add plugins
            AddBindingsFor<IIocModule>();

            // call plugins
            IEnumerable<IIocModule> plugins = GetAllInstances<IIocModule>();
            if (plugins != null)
            {
                foreach (IIocModule plugin in plugins)
                {
                    plugin.LoadBindings(this);
                }
            }
        }

        private static void LoadAllAssemblies(string filePattern)
        {
            string path = null;
            if (HttpContext.Current == null)
            {
                path = AppDomain.CurrentDomain.BaseDirectory;
            }
            else
            {
                path = HttpRuntime.BinDirectory;
            }

            string[] asmFiles = Directory.GetFiles(path, filePattern);

            foreach (string asmFile in asmFiles)
            {
                AssemblyName asmName = AssemblyName.GetAssemblyName(asmFile);

                if (AppDomain.CurrentDomain.GetAssemblies().Any(asm => AssemblyName.ReferenceMatchesDefinition(asmName, asm.GetName())) == false)
                {
                    Assembly.Load(asmName);
                }
            }
        }
        #endregion
    }
}
